package com.duqc.springbatch;

import lombok.extern.slf4j.Slf4j;
import org.apache.ibatis.session.SqlSessionFactory;
import org.springframework.batch.core.Job;
import org.springframework.batch.core.Step;
import org.springframework.batch.core.configuration.annotation.JobBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepBuilderFactory;
import org.springframework.batch.core.configuration.annotation.StepScope;
import org.springframework.batch.core.launch.support.RunIdIncrementer;
import org.springframework.batch.item.ItemProcessor;
import org.springframework.batch.item.ItemReader;
import org.springframework.batch.item.ItemWriter;
import org.springframework.context.annotation.Bean;
import org.springframework.stereotype.Component;

/**
 * @ClassName DataBatchJob
 * @Description DataBatchJob
 * @Author duqc
 * @Date 2022/7/18
 **/
@Slf4j
@Component
public class DataBatchJob {

    /**
     * Job构建工厂，用于构建Job
     */
    private final JobBuilderFactory jobBuilderFactory;

    /**
     * step构建工厂，用于构建step
     */
    private final StepBuilderFactory stepBuilderFactory;

    private final JobListener jobListener;

    private final SqlSessionFactory sqlSessionFactory;

    public DataBatchJob(JobBuilderFactory jobBuilderFactory, StepBuilderFactory stepBuilderFactory,
                        JobListener jobListener, SqlSessionFactory sqlSessionFactory) {
        this.jobBuilderFactory = jobBuilderFactory;
        this.stepBuilderFactory = stepBuilderFactory;
        this.jobListener = jobListener;
        this.sqlSessionFactory = sqlSessionFactory;
    }

    /**
     * 一个最基础的Job通常有一个或者多个step组成
     *
     * @return JobBuilderFactory
     */
    public Job dataHandleJob() {
        return jobBuilderFactory.get("dataHandleJob")
                .incrementer(new RunIdIncrementer())
                //start是JOB执行的第一个step
                .start(handleDataStep())
                // 可以调用next方法设置其他的step，例如：
                // next(xxxStep()).
                // next(xxxStep()).
                // ...
                // 设置我们自定义的JobListener
                .listener(jobListener)
                .build();
    }

    /**
     * 一个简单的step主要分为三个部分
     * ItemReader：用于读取数据
     * ItemProcessor：用于处理数据
     * ItemWriter：用于写数据
     *
     * @return StepBuilderFactory
     */
    private Step handleDataStep() {
        return stepBuilderFactory.get("handleDataStep").
                //<输入对象，输出对象> chunk类似于SQL的commit，这里表示处理（processor）100条后写入（writer）一次
                        <BussinessOption, BussinessOption>chunk(10)
                //skipLimit方法的意思是我们可以设定一个我们允许的这个step可以跳过的异常数量，假如我们设定为10，则当这个step运行时，只要出现的异常数目不超过10，整个step都不会fail。注意，若不设定skipLimit，则其默认值是0.
                //noSkip方法的意思则是指出现这个异常我们不想跳过，也就是从skip的所有exception当中排除这个exception
                //遇到异常重试三个后还是失败，异常数量(skipLimit)就加1，异常数量为3后，停止任务。如果配置有noSkip，则一旦遇到noSkip中的异常，就直接停止
                .faultTolerant().retryLimit(3).retry(Exception.class).skipLimit(3).skip(Exception.class).noSkip(Exception.class)
                //指定ItemReader对象
                .reader(getDataReader())
                //指定ItemPricessor对象
                .processor(getDataProcessor())
                //指定ItemWriter对象
                .writer(getDataWriter())
                .build();
    }

    /**
     * 写入数据
     *
     * @return ItemWriter
     */
    @Bean
    @StepScope
    private ItemWriter<? super BussinessOption> getDataWriter() {
        return list -> {
            list.forEach(info -> {
                log.info("write data : " + info);
            });
        };
    }

    /**
     * 处理数据
     *
     * @return ItemProcessor
     */
    @Bean
    @StepScope
    private ItemProcessor<? super BussinessOption, ? extends BussinessOption> getDataProcessor() {
        return bussinessOption -> {
            log.info("processor data : " + bussinessOption.toString());
            return bussinessOption;
        };
    }

    /**
     * 读取数据
     * ListItemReader	读取List类型数据，只能读取一次
     * ItemReaderAdapter	ItemReader适配器，可以服用现有的读操作
     * FlatFileItemReader	读Flat类型文件
     * StaxEventItemReader	读XML类型的文件
     * JdbcCursorItemReader	基于JDBC游标方式读取数据库
     * HibernateCursorItemReader	基于Hibernate游标方式读取数据库
     * StoredProcedureItemReader	基于存储过程读取数据库
     * IbatisPagingItemReader	基于Ibatis分页读取数据库
     * JpaPagingItemReader	基于Jpa方式分页读取数据库
     * JdbcPagingItemReader	基于JDBC方式分页读取数据库
     * HibernatePagingItemReader	基于Hibernate方式分页读取数据库
     * JmsItemReader	读取JMS队列
     * IteratorItemReader	迭代方式读组件
     * MultiResourceItemReader	多文件读组件
     * MongoItemReader	基于分布式文件存储的数据库MongoDB读组件
     * Neo4jItemReader	面向网络的数据库Neo4j读组件
     * ResourcesItemReader	基于批量资源的读组件，每次读取返回资源对象
     * AmqpItemReader	读取AMQP队列组件
     * RepositoryItemReader	基于SpringData的读组件
     * MyBatisPagingItemReader	mybatis分页读取
     * JsonItemReader json
     *
     * @return
     */
    @Bean
    @StepScope
    private ItemReader<? extends BussinessOption> getDataReader() {
        return new CommonMybatisItemReader<>(sqlSessionFactory);
    }

}
